/*************************************************************************
Crytek Source File.
Copyright (C), Crytek Studios, 2001-2008.
-------------------------------------------------------------------------
$Id: CLightPropagationVolumeObject.cpp,v 1.0 2008/05/19 12:14:13 AntonKaplanyan Exp wwwrun $
$DateTime$
Description:  Routine for rendering and managing of Light Propagation Volumes
-------------------------------------------------------------------------
History:
- 12:6:2008   12:14 : Created by Anton Kaplanyan
*************************************************************************/

#include "StdAfx.h"
#include "LightPropagationVolumeObject.h"

#include "..\Viewport.h"

#include "PanelTreeBrowser.h"

#include "EntityObject.h"
#include "Geometry\EdMesh.h"
#include "Material\Material.h"
#include "Material\MaterialManager.h"

#include <I3Dengine.h>
#include <IEntitySystem.h>
#include <IEntityRenderState.h>


//////////////////////////////////////////////////////////////////////////
// CBase implementation.
//////////////////////////////////////////////////////////////////////////
IMPLEMENT_DYNCREATE(CLightPropagationVolumeObject,CBaseObject)

//////////////////////////////////////////////////////////////////////////
CLightPropagationVolumeObject::CLightPropagationVolumeObject()
{
	m_bNeedUpdateUI = false;
	m_pLPVRenderNode = NULL;
	m_bbox = AABB(Vec3(-.5f, -.5f, -.5f), Vec3(.5f, .5f, .5f));

	AddVariable( m_fDensity,"Density", functor(*this,&CLightPropagationVolumeObject::OnDensityChange) );
	m_fDensity.SetLimits(.1f, 10);
	m_fDensity.Set(1.f);
	AddVariable( m_bSpecular,"Specular", functor(*this,&CLightPropagationVolumeObject::OnSpecularChange) );	
	m_bSpecular.Set(false);
	//AddVariable( m_bAutofit,"Autofit", functor(*this,&CLightPropagationVolumeObject::OnAutofitChange) );	
	//m_bAutofit.Set(false);
}

CLightPropagationVolumeObject::~CLightPropagationVolumeObject()
{
}

//////////////////////////////////////////////////////////////////////////
bool CLightPropagationVolumeObject::CreateGameObject()
{
	m_pLPVRenderNode = (ILPVRenderNode*)GetIEditor()->Get3DEngine()->CreateRenderNode( eERType_LightPropagationVolume );
	if(m_pLPVRenderNode)
	{
		m_pLPVRenderNode->SetDensity(m_fDensity);
		m_pLPVRenderNode->SetMatrix( GetWorldTM() );
		Matrix34 mx;
		m_pLPVRenderNode->GetMatrix(mx);
		SetWorldTM(mx, TM_NOT_INVALIDATE);
		m_fDensity.Set(m_pLPVRenderNode->GetDensity());
		m_bNeedUpdateUI = true;
	}
	else
		assert(0);
	return true;
};

//////////////////////////////////////////////////////////////////////////
bool CLightPropagationVolumeObject::Init( IEditor *ie,CBaseObject *prev,const CString &file )
{
	SetColor( RGB(127,255,255) );
	SetTextureIcon( GetClassDesc()->GetTextureIconId() );

	// Must be after Set call.
	bool res = CBaseObject::Init( ie,prev,file );

	if (prev)
	{
		CLightPropagationVolumeObject *obj = (CLightPropagationVolumeObject*)prev;
		m_bbox = obj->m_bbox;
	}

	return res;
}

//////////////////////////////////////////////////////////////////////////
void CLightPropagationVolumeObject::GetLocalBounds( AABB &box )
{
	box = m_bbox;
}

//////////////////////////////////////////////////////////////////////////
int CLightPropagationVolumeObject::MouseCreateCallback( CViewport *view,EMouseEvent event,CPoint &point,int flags )
{
	if (event == eMouseMove || event == eMouseLDown || event == eMouseLUp)
	{
		Vec3 pos;
		pos = view->MapViewToCP( point );
		SetPos(pos);

		if (event == eMouseLDown)
			return MOUSECREATE_OK;

		return MOUSECREATE_CONTINUE;
	}
	return CBaseObject::MouseCreateCallback( view,event,point,flags );
}

//////////////////////////////////////////////////////////////////////////
void CLightPropagationVolumeObject::Display( DisplayContext &dc )
{
	if(m_bNeedUpdateUI)
	{
		m_bNeedUpdateUI = false;
		UpdateUIVars();
	}

	const Matrix34 &wtm = GetWorldTM();
	Vec3 wp = wtm.GetTranslation();

	if (IsSelected())
		dc.SetSelectedColor();
	else if (IsFrozen())
		dc.SetFreezeColor();
	else
		dc.SetColor( GetColor() );

	dc.PushMatrix( wtm );
	dc.DrawWireBox( m_bbox.min,m_bbox.max);
	if (IsSelected() && m_pLPVRenderNode)	// draw grid cells
	{
		float density = m_pLPVRenderNode->GetDensity();
		Vec3 size = m_pLPVRenderNode->GetBBox().GetSize() * density;
		Vec3 origSize = size;
		Vec3 offset = size;
		size.x = floor_tpl(size.x);
		size.y = floor_tpl(size.y);
		size.z = floor_tpl(size.z);
		offset -= size;
		offset *= .5f;
		for(float i=offset.x;i<origSize.x;++i)
			for(float j=offset.y;j<origSize.y;++j)
				for(float k=offset.z;k<origSize.z;++k)		
					dc.DrawPoint(Vec3(i/origSize.x-.5f, j/origSize.y-.5f, k/origSize.z-.5f));
	}
	dc.PopMatrix();

	DrawDefault(dc);
}

//////////////////////////////////////////////////////////////////////////
bool CLightPropagationVolumeObject::HitTest( HitContext &hc )
{
	Matrix34 invertWTM = GetWorldTM();
	Vec3 worldPos = invertWTM.GetTranslation();
	invertWTM.Invert();

	Vec3 xformedRaySrc = invertWTM.TransformPoint(hc.raySrc);
	Vec3 xformedRayDir = invertWTM.TransformVector(hc.rayDir).GetNormalized();

	float epsilonDist = max(.1f, hc.view->GetScreenScaleFactor( worldPos ) * 0.01f);
	epsilonDist *= max(0.0001f, min(invertWTM.GetColumn0().GetLength(), min(invertWTM.GetColumn1().GetLength(), invertWTM.GetColumn2().GetLength()) ));
	float hitDist;

	float tr = hc.distanceTolerance/2 + 1;
	AABB box;
	box.min = m_bbox.min - Vec3(tr+epsilonDist,tr+epsilonDist,tr+epsilonDist);
	box.max = m_bbox.max + Vec3(tr+epsilonDist,tr+epsilonDist,tr+epsilonDist);
	Vec3 p;
	if (Intersect::Ray_AABB( xformedRaySrc,xformedRayDir,box,p ))
	{
		if (Intersect::Ray_AABBEdge( xformedRaySrc,xformedRayDir,m_bbox,epsilonDist,hitDist,p ))
		{
			hc.dist = xformedRaySrc.GetDistance(p);
			return true;
		}
	}
	return false;
}

void CLightPropagationVolumeObject::InvalidateTM( int nWhyFlags )
{
	CBaseObject::InvalidateTM(nWhyFlags);
	if(m_pLPVRenderNode && !(nWhyFlags&TM_NOT_INVALIDATE))	// do it only once per call
	{
		m_pLPVRenderNode->SetMatrix( GetWorldTM() );
		Matrix34 mx;
		m_pLPVRenderNode->GetMatrix(mx);
		SetWorldTM(mx, TM_NOT_INVALIDATE);
	}
}

void CLightPropagationVolumeObject::OnDensityChange( IVariable *pVar )
{
	if(m_pLPVRenderNode)
	{
		m_pLPVRenderNode->SetDensity(m_fDensity);
		m_pLPVRenderNode->SetMatrix( GetWorldTM() );
		Matrix34 mx;
		m_pLPVRenderNode->GetMatrix(mx);
		SetWorldTM(mx, TM_NOT_INVALIDATE);
		if(m_pLPVRenderNode->GetDensity() != m_fDensity)
		{
			m_fDensity.Set(m_pLPVRenderNode->GetDensity());
			m_bNeedUpdateUI = true;
		}
	}
}

void CLightPropagationVolumeObject::OnSpecularChange( IVariable *pVar )
{
	if(m_pLPVRenderNode)
	{
		m_pLPVRenderNode->EnableSpecular(m_bSpecular);
	}
}

void CLightPropagationVolumeObject::OnAutofitChange( IVariable *pVar )
{
	if(!m_bAutofit)
		return;

	if(m_pLPVRenderNode)
	{
		AABB bbox = m_pLPVRenderNode->GetBBox();

		DynArray<CDLight> lights;
		const PodArray<CDLight*>* allLights = GetIEditor()->Get3DEngine()->GetStaticLightSources();
		if(allLights)
		{
			for(int i=0;i<allLights->size();++i)
			{
				CDLight* light = (*allLights)[i];
				if(!(light->m_Flags & DLF_DEFERRED_LIGHT) || !(light->m_Flags & DLF_ALLOW_LPV))
					continue;
				if(!bbox.IsContainPoint(light->m_Origin))
					continue;
				lights.push_back(*light);
			}
		}
		if(!lights.empty())
		{
			m_pLPVRenderNode->AutoFit(lights);
			m_fDensity = m_pLPVRenderNode->GetDensity();
			Matrix34 mx;
			m_pLPVRenderNode->GetMatrix(mx);
			SetWorldTM(mx, TM_NOT_INVALIDATE);
		}
	}
}

void CLightPropagationVolumeObject::Done()
{
	SAFE_DELETE(m_pLPVRenderNode);
	CBaseObject::Done();
}
